package com.sigmazero13.MKDummy.models;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import com.sigmazero13.MKDummy.listeners.PlayerListener;

public class DummyPlayer implements Serializable {
	
	private static final long serialVersionUID = -1761067337137251249L;
	private String heroName;
	private CardDeck deck;
	private Map<ManaColor, Integer> crystals;
	private boolean declaredEnd = false;
	private StringBuilder logBuffer;
	private StringBuilder playerLog;
	transient private List<PlayerListener> listeners;
	protected boolean showTimestamp = false;
	
	transient private SimpleDateFormat sdf;
	
	protected Integer roundNum = 0;
	protected Integer turnNum = 0;
	
	public DummyPlayer(String heroName) {
		this.heroName = heroName;
		crystals = new HashMap<ManaColor, Integer>();
		crystals.put(ManaColor.BLUE, 0);
		crystals.put(ManaColor.GREEN, 0);
		crystals.put(ManaColor.RED, 0);
		crystals.put(ManaColor.WHITE, 0);
		deck = new CardDeck();
		listeners = new ArrayList<PlayerListener>();
	}
	
	public DummyPlayer(String heroName, List<ManaColor> crystals) {
		this(heroName);
		for (ManaColor color : crystals) {
			addCrystal(color);
		}
	}
	
	public void addPlayerListener(PlayerListener listener) {
		getListeners().add(listener);
	}
	
	public void removeAllPlayerListeners() {
		getListeners().clear();
	}
	
	public String getName() {
		return heroName;
	}
	
	public void addCrystal(ManaColor color) {
		crystals.put(color, crystals.get(color)+1);
		
		for (PlayerListener listener : getListeners()) {
			listener.onCrystalsChanged(crystals);
		}
	}
	
	public void takeTurn() {
		logMessage(" ");
		if (showTimestamp) {
			logMessage("#i#" + getTimeStamp() + "#i#");
		}
		logMessage("#b#Round " + roundNum + ", turn " + turnNum + " summary:#b#");
		turnNum++;
		if (deck.countCardsRemaining() == 0) {
			declaredEnd = true;
			for (PlayerListener listener : getListeners()) {
				listener.onRoundEndStatusChange(true);
			}
			logMessage("#i#End of Round declared!#i#");
		} else {
			List<Card> drawn = drawCards(3);
			if (!drawn.isEmpty()) {
				Card lastCard = drawn.get(drawn.size()-1);
				Integer numCrystals = crystals.get(lastCard.getColor());
				if (numCrystals > 0 && deck.countCardsRemaining() > 0) {
					Integer numCards = Math.min(numCrystals, deck.countCardsRemaining());
					String message = "Drawing " + numCards + " additional card";
					if (numCards != 1) {
						message += "s";
					}
					logMessage(message + ".");
					drawCards(numCrystals);
				}
			}
		}
		
		for (PlayerListener listener : getListeners()) {
			listener.onDeckChanged(deck);
		}
		flushLogBuffer();
	}
	
	public void newRound(ManaColor newCard, ManaColor newCrystal) {
		newRound(newCard, newCrystal, null);
	}
		
	public Integer newRound(ManaColor newCard, ManaColor newCrystal, Set<Integer> availableTactics) {
		roundNum++;
		turnNum=1;
		Integer tactic = null;
		logMessage(" ");
		if (showTimestamp) {
			logMessage("#i#" + getTimeStamp() + "#i#");
		}
		logMessage("#b#New round! (" + roundNum + ")#b#");
		
		if (availableTactics != null) {
			if (availableTactics.size() == 0) {
				logMessage("#i#No available tactics, none selected!#i#");
			} else {
				Random r = new Random();
				Integer randNum = r.nextInt(availableTactics.size());
				List<Integer> tacticsList = new ArrayList<Integer>(availableTactics);
				logMessage("#i#" + getName() + " picked Tactics Card #b#" + tacticsList.get(randNum) + "#b##i#");
				tactic = tacticsList.get(randNum);
			}
		}
		
		if (newCard != null) {
			deck.addNewCard(new Card(newCard));
		}
		if (newCrystal != null) {
			addCrystal(newCrystal);
		}
		declaredEnd = false;
		deck.reshuffle();
		
		refreshListeners();
		flushLogBuffer();
		
		return tactic;
	}
	
	public void refreshListeners() {
		for (PlayerListener listener : getListeners()) {
			listener.onCrystalsChanged(crystals);
			listener.onDeckChanged(deck);
			listener.onRoundEndStatusChange(declaredEnd);
		}
	}
	
	public CharSequence getLog() {
		return getPlayerLog();
	}
	
	public int getRound() {
		return roundNum;
	}
	
	public int getTurnInRound() {
		return turnNum;
	}
	
	protected CardDeck getDeck() {
		return deck;
	}
	
	public void setTimestampDisplay(boolean showTimestamp) {
		this.showTimestamp = showTimestamp;
	}
	
	// Returns the last card drawn
	private List<Card> drawCards(int numToDraw) {
		List<Card> drawn = new ArrayList<Card>();
		StringBuilder colors = new StringBuilder();
		for (Integer i = 0; i < numToDraw; i++) {
			Card c = deck.drawCard();
			if (c == null) {
				break;
			}
			drawn.add(c);
			colors.append(c.getColor().toColorString() + ", ");
		}
		String cardWord = "Card";
		if (drawn.size() != 1) {
			cardWord += "s";
		}
		CharSequence message = cardWord + " drawn: " + colors.subSequence(0, colors.length()-2);
		logMessage(message);
		return drawn;
	}
	
	protected void logMessage(CharSequence message) {
		if (getLogBuffer().length() > 0) {
			getLogBuffer().append("\n");
		}
		getLogBuffer().append(message);
	}
	
	protected void flushLogBuffer() {
		if (getPlayerLog().length() > 0) {
			getPlayerLog().append("\n");
		}
		getPlayerLog().append(getLogBuffer());
		for (PlayerListener listener : getListeners()) {
			listener.onLogChanged(getLogBuffer());
		}
		logBuffer = null;
	}
	
	protected List<PlayerListener> getListeners() {
		if (listeners == null) listeners = new ArrayList<PlayerListener>();
		
		return listeners;
	}
	
	protected String getTimeStamp() {
		return getDateFormat().format(new Date());
	}
	
	private StringBuilder getLogBuffer() {
		if (logBuffer == null) {
			logBuffer = new StringBuilder();
		}
		
		return logBuffer;
	}
	
	private StringBuilder getPlayerLog() {
		if (playerLog == null) {
			playerLog = new StringBuilder();
		}
		
		return playerLog;
	}
	
	private SimpleDateFormat getDateFormat() {
		if (sdf == null) {
			sdf  = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
		}
		return sdf;
	}
}
